﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.ComponentModel;

namespace LEVNet
{

    /// <summary>
    /// ref example from http://msdn.microsoft.com/zh-tw/library/fx6588te(v=vs.110).aspx
    /// </summary>
    public class LEVServerSocket : IDisposable
    {
        public const string _OldVer_EOF_Token = "<EOF>"; 

        //public event EventHandler SocketMsgEventHandler;
        public delegate void SocketMsgEventHandler(LEVSocketEventType eventTyp, Socket client, string Msg);
        SocketMsgEventHandler msgEventHandler;
        public delegate void SocketExceptionEventHandler(LEVSocketExceptionType eventTyp, Socket client, string Msg);
        SocketExceptionEventHandler excptEventHandler;
        public delegate void ReceivingEventHandler(SocketReceivingTyp receiveType, Socket client, byte[] receivingData);
        ReceivingEventHandler receiveEventHandler;

        //默認信號為未發送狀態
        private static ManualResetEvent SignalEvent = new ManualResetEvent(false);

        private BackgroundWorker bgwServer;
        private IPEndPoint gLocalEndPoint;
        private Socket gListenerSocket;

        //參數會指定可在佇列中等候接收的輸入連接數。若要決定可指定的最大連接數，除非同時間的連線非常的大，否則值4應該很夠用。
        private int gMaxListenIncomingConnections;

        private bool gListenerAcceptLoopFlag = false;

        //private List<byte[]> gSocketCmdTokenBArray;
        //private byte[] gOldVer_EOF_TokenBArray;
        private List<Socket> gClientSocketList;


        public LEVServerSocket(int connectPortNumber, int maxListenIncomingConnections)
        {
            //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
            gLocalEndPoint = new IPEndPoint(IPAddress.Any, connectPortNumber);
            gMaxListenIncomingConnections = maxListenIncomingConnections;
            initServerSocket();
        }
        public LEVServerSocket(string ip, int connectPortNumber, int maxListenIncomingConnections)
        {
            //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
            gLocalEndPoint = new IPEndPoint(IPAddress.Parse(ip), connectPortNumber);
            gMaxListenIncomingConnections = maxListenIncomingConnections;
            initServerSocket();
        }
        public LEVServerSocket(IPEndPoint localEndPoint, int maxListenIncomingConnections)
        {
            //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
            gLocalEndPoint = localEndPoint;
            gMaxListenIncomingConnections = maxListenIncomingConnections;
            initServerSocket();

        }
        private void initServerSocket()
        {
            //string[] tokenName = Enum.GetNames(typeof(SocketCmdToken));
            //gSocketCmdTokenBArray = new List<byte[]>();
            //foreach (string s in tokenName)
            //{
            //    byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(s);
            //    gSocketCmdTokenBArray.Add(byteArray);
            //}
            //gOldVer_EOF_TokenBArray = Encoding.UTF8.GetBytes(_OldVer_EOF_Token); ;

            gClientSocketList = new List<Socket>();
            bgwServer = new BackgroundWorker();
            bgwServer.WorkerReportsProgress = true;
            bgwServer.WorkerSupportsCancellation = true;
            bgwServer.DoWork += new DoWorkEventHandler(bgwServerDoWork);
            bgwServer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgwServerRunWorkerCompleted);
        }
        /// <summary>
        /// Implement IDisposable.
        /// </summary>
        public void Dispose()
        {
            try
            {
                gListenerAcceptLoopFlag = false;
                //gListenerSocket.Shutdown(SocketShutdown.Both);
                gListenerSocket.Close();
            }
            catch
            {
            }
            //GC.SuppressFinalize(this);
        }
        public void Start()
        {
            if (!bgwServer.IsBusy)
            {
                bgwServer.RunWorkerAsync();
            }
            else
            {
                FireExceptionEvent(LEVSocketExceptionType.ReConnectException, null, "Server was already started for Listening.");
            }
        }
        public void Stop()
        {
            gListenerAcceptLoopFlag = false;
            //SignalEvent.Set();
            //bgwServer.CancelAsync();
            foreach (Socket s in gClientSocketList)
            {
                try
                {
                    FireSocketMsgEvent(LEVSocketEventType.ClientDisconnect, s, "Client is going to be disconnected.");
                    closeClientSocket(s, false, true);
                }
                catch (Exception e)
                {
                    FireExceptionEvent(LEVSocketExceptionType.DisConnectException, s, "Client being disconnected Failure." + Environment.NewLine + e);
                }
            }
            gClientSocketList.Clear();

            //gListenerSocket.Disconnect(false);
            //gListenerSocket.Shutdown(SocketShutdown.Both);
            gListenerSocket.Close();
        }
        public Socket GetServerHandler()
        {
            return gListenerSocket;
        }
        public List<Socket> GetAllConnectedClientHandler()
        {
            return gClientSocketList;
        }
        private void closeClientSocket(Socket clientHandler, bool removeFromList, bool closeMsgSendOut)
        {
            if (removeFromList)
            {
                gClientSocketList.Remove(clientHandler);
            }
            if (closeMsgSendOut)
            {
                Send_PreviousVer(clientHandler, "Server is going to close the connection.");
            }
            clientHandler.Disconnect(false);
            clientHandler.Shutdown(SocketShutdown.Both);
            clientHandler.Close();
        }
        private void bgwServerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //這裡是程式完成時會觸發的事件，
            //這是 background執行緒結束後會觸發的值
            //SocketInformLog("bgwServerRunWorkerCompleted." + Environment.NewLine);
        }
        private void bgwServerDoWork(object sender, DoWorkEventArgs e)
        {
            StartListening();
            FireSocketMsgEvent(LEVSocketEventType.ServerStop, null, "Server has been stoped");
            //SocketInformLog("Server has been stoped Listening." + Environment.NewLine);
        }
        private void StartListening()
        {
            // 實體化 ListenerSocket
            // Create a TCP/IP socket.
            gListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


            //使用Thread
            //new Thread(() => StartListening()).Start();

            // Bind socket to endpoint and listen for incoming connections.
            try
            {
                //socket連繫到該位址
                gListenerSocket.Bind(gLocalEndPoint);

                // 啟動監聽
                //backlog=4 參數會指定可在佇列中等候接收的輸入連接數。若要決定可指定的最大連接數，除非同時間的連線非常的大，否則值4應該很夠用。
                gListenerSocket.Listen(gMaxListenIncomingConnections);

                gListenerAcceptLoopFlag = true;
                FireSocketMsgEvent(LEVSocketEventType.ServerStart, null, "Server is Created : " + gLocalEndPoint.Address + ", Listening Port: " + gLocalEndPoint.Port);

                while (gListenerAcceptLoopFlag)
                {
                    //ManualResetEvent必須要再呼叫Reset方法後才會處於未收到信號的狀態
                    // Set the event to nonsignaled state.
                    SignalEvent.Reset();

                    //SocketInformLog("Server is starting listening and begining accept. ListenerSocket HashCode = " + gListenerSocket.GetHashCode() + Environment.NewLine);
                    // 以上完成了SERVER的Listening
                    // 之後BeginAccept()  開啟執行緒準備接收client的要求。
                    // 此處的BeginAccept 第一個參數就是當Socket一開始接收到Client的連線要求時，
                    // 立刻會呼叫delegate的AcceptCallback的方法，這個AcceptCallback名稱是我們自己取的
                    // Start an asynchronous socket to listen for connections.                                        
                    gListenerSocket.BeginAccept(new AsyncCallback(AcceptCallback), gListenerSocket);

                    //WaitOne的方法鎖定了執行緒，狀態也是WaitSleepJoin.
                    // Wait until a connection is made before continuing.
                    SignalEvent.WaitOne();
                }

            }
            catch (Exception e)
            {
                gListenerAcceptLoopFlag = false;
                FireExceptionEvent(LEVSocketExceptionType.AcceptException, null,
                    "To start listening and to accept an incoming connection attempt failure." + Environment.NewLine + e);
            }
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            Socket handlerSocket = null;

            try
            {
                //喚醒多個執行緒,讓進入Waitting回到Running狀態。
                // Signal the main thread to continue.
                SignalEvent.Set();

                //AsyncState 取得符合或包含非同步作業資訊的使用者定義的物件
                // Get the socket that handles the client request.
                Socket listener = (Socket)ar.AsyncState;  // 取得主要的socket, 其中包含使用者定義的物件及handles the client request
                //將主要正在 listening(正在連入的client Socket)轉交給另一個臨時的handlerSocket變數，並且結束接受此一客戶端的連線

                handlerSocket = listener.EndAccept(ar);
                //取得遠端節點的EndPoint handler.RemoteEndPoint.ToString().Split(':')[0]

                //SocketInformLog("A client has connected. client ip : " + handlerSocket.RemoteEP.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handlerSocket.GetHashCode() + Environment.NewLine);

                gClientSocketList.Add(handlerSocket);
                // Create the state object.
                SocketStateObj state = new SocketStateObj();
                state.workSocket = handlerSocket;

                // 假設已連線的客戶端要傳送資料時，所指定的回呼函式-ReadCallback，這也是C#中的delagate型別
                // 定義當Socket 接收到資料時要交給哪一個函數處理，這裡定義了BeginReceive()
                // 參數第一個為存放的位置，為byte[]，傳入的資料會置於此陣列中；第二參數為啟始位置、第三參數為一次接收的最大長度；
                // 第四為封包的旗標，例如標記為"廣播封包"之類，
                // 第五參數為開始接受資料時叫用的回呼函式，用AsyncCallback實體化指定ReadCallback為回呼函式，所以當客戶端將資料傳進來時，ReadCallback函式會被叫用；
                // 第六為狀態的參數，這個是用戶自訂，當回呼函式ReadCallback被叫用 時，此參數的值會被傳遞到ReadCallback，其中包含Socket，但是回呼函式仍會曉得是哪一個Socket在傳送資料。
                handlerSocket.BeginReceive(state.buffer, 0, SocketStateObj.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), state);
            }
            catch (Exception e)
            {
                FireExceptionEvent(LEVSocketExceptionType.ConnectException, handlerSocket, 
                    "[AcceptCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket." + Environment.NewLine + e);
            }
        }

        private void ReadCallback(IAsyncResult ar)
        {
            String content = String.Empty;

            //重新取回 state object
            // Retrieve the state object and the handler socket from the asynchronous state object.
            SocketStateObj state = (SocketStateObj)ar.AsyncState;
            Socket handler = state.workSocket;

            try
            {
                //SocketInformLog("Client is waitting and begining to receive data. client ip : " + handler.RemoteEndPoint.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handler.GetHashCode() + Environment.NewLine);

                //叫用 EndReceive 完成指定的非同步接收作業, 參數asyn (ar) 識別要完成的非同步接收作業，並要從其中擷取最終結果。
                // Read data from the client socket. 
                int bytesRead = handler.EndReceive(ar);
                //SocketInformLog("ReadCallback; byteRead = " + bytesRead);

                if (bytesRead > 0)
                {
                    #region msdn code
                    // There  might be more data, so store the data received so far.
                    //state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                    //state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead));

                    byte[] currentRead = new byte[bytesRead];
                    Array.Copy(state.buffer, currentRead, bytesRead);
                    state.listBuffer.AddRange(currentRead);
                    //FireReceivingEvent(SocketReceivingTyp.Fragment, handler, currentRead);

                    //content = state.sb.ToString();
                    //content = Encoding.UTF8.GetString(state.buffer, 0, bytesRead);

                    //only for compare, so use Encoding.ASCII
                    content = Encoding.ASCII.GetString(state.buffer, 0, bytesRead);
                    //SocketInformLog("Client is receiving data. client ip : " + handler.RemoteEndPoint.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handler.GetHashCode() + Environment.NewLine);
                    //SocketInformLog("ReadCallback; content = " + content + Environment.NewLine);

                    // Check for end-of-file tag. If it is not there, read more data.
                    //if(LEVSocketUtilities.ByteArrayIndexOf(currentRead, 0, gOldVer_EOF_TokenBArray) > -1)
                    //if (content.IndexOf("<EOF>") > -1)
                    if (content.IndexOf(_OldVer_EOF_Token) > -1)
                    {
                        // All the data has been read from the 
                        // client. Display it on the console.
                        //Console.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content);
                        // Echo the data back to the client.
                        //Send(handler, content);
                        byte[] packet = state.listBuffer.ToArray<byte>();
                        int idx = LEVSocketUtilities.ByteArrayIndexOf(packet, Encoding.ASCII.GetBytes(_OldVer_EOF_Token));
                        if (idx > -1)
                        {
                            byte[] cpy = new byte[idx];
                            Array.Copy(packet, cpy, idx);
                            FireReceivingEvent(SocketReceivingTyp.OldVerWholePacket, handler, cpy);
                        }
                        //SocketInformLog("ReadCallback; Get <EOF> and Send back to client." + Environment.NewLine);
                        //Send_PreviousVer(handler, "get Data finish");
                        state.listBuffer.Clear();
                        //state.sb.Clear();
                    }
                    else if(LEVSocketUtilities.ByteArrayIndexOf(currentRead, SocketCmdToken._packetDataEOF_.ToString()) > -1){
                        byte[] packet = state.listBuffer.ToArray<byte>();
                        int idx = LEVSocketUtilities.ByteArrayIndexOf(packet, SocketCmdToken._packetDataEOF_.ToString());
                        if (idx > -1)
                        {
                            byte[] cpy = new byte[idx];
                            Array.Copy(packet, cpy, idx);
                            FireReceivingEvent(SocketReceivingTyp.WholePacket, handler, cpy);
                        }
                        //FireReceivingEvent(SocketReceivingTyp.WholePacket, handler, state.listBuffer.ToArray<byte>());
                        state.listBuffer.Clear();
                    }
                    else
                    {
                        //SocketInformLog("ReadCallback; Not all data received. Get more." + Environment.NewLine);
                        // Not all data received. Get more.
                    }
                    handler.BeginReceive(state.buffer, 0, SocketStateObj.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), state);
                    #endregion

                    #region modify code
                    // All data received. Get next message. 
                    // Not all data received. Get more.
                    //handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);        //new

                    //// There  might be more data, so store the data received so far.
                    ////state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                    //state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead));


                    //// Check for end-of-file tag. If it is not there, read more data.
                    //content = state.sb.ToString();

                    //////////////////////////////

                    //if (content.IndexOf("<EOF>") > -1)
                    //{
                    //    //// Reply to the client.
                    //    //Send(handler, resultData);

                    //    // All data received. Get next message. 
                    //    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);        //new

                    //}
                    //else
                    //{
                    //    // Not all data received. Get more.
                    //    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
                    //}
                    #endregion
                }
            }
            catch (SocketException e)
            {
                /////////////////////////////////////////////////////////////
                //if (ex.ErrorCode == 10054 || ex.ErrorCode == 10053)
                //{
                //    log.Write(client_ip + " has left");
                //    LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.ClientLefted,
                //            client_ip, "Client has left");
                //}
                //else
                //{
                //    //Console.WriteLine(ex);
                //    log.Write("ReadCallBack1: " + ex.Message);  //2013/8/22: Log exception message
                //    LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //            "-", "ReadCallBack1: " + ex.Message);
                //}

                //SocketInformLog("[SocketException][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e + Environment.NewLine);
                //throw new Exception("[SocketException] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e);
                //SocketInformLog("handlerSocket Close. HashCode = " + handler.GetHashCode() + Environment.NewLine);
                FireExceptionEvent(LEVSocketExceptionType.DisConnectException, handler, "[SocketException][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket." + Environment.NewLine + e);
                closeClientSocket(handler, true, false);
                FireSocketMsgEvent(LEVSocketEventType.ClientDisconnect, handler, "Client is going to be disconnected.");

            }
            catch (Exception e)
            {
                //SocketInformLog("[Exception][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e + Environment.NewLine);
                FireExceptionEvent(LEVSocketExceptionType.DisConnectException, handler, "[Exception][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket." + Environment.NewLine + e);
                //throw new Exception("[Exception] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e);
                //log.Write("ReadCallBack2: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "ReadCallBack2: " + e.Message);
            }
        }

        public void Send_PreviousVer(Socket handler, String data)
        {
            try
            {
                //log.Write(handler.RemoteEndPoint.ToString().Split(':')[0] + " >> " + data);
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.TransmittedToClient,
                //        handler.RemoteEndPoint.ToString().Split(':')[0], data);

                data += _OldVer_EOF_Token;
                // Convert the string data to byte data using encoding.
                byte[] byteData = Encoding.UTF8.GetBytes(data);

                //SocketInformLog("Send back to Client = " + data + Environment.NewLine);

                // Begin sending the data to the remote device.
                handler.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(SendCallback), handler);
            }
            catch (Exception e)
            {
                FireExceptionEvent(LEVSocketExceptionType.SendException, handler, "[Exception][Send] Sends data asynchronously to a connected Socket Failure." + Environment.NewLine + e);
                //SocketInformLog("[Exception][Send] Sends data asynchronously to a connected Socket Failure. ==> " + e + Environment.NewLine);
                //throw new Exception("[Exception] Sends data asynchronously to a connected Socket Failure. ==> " + e);
                //log.Write("Send: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "Send: " + e.Message);
            }
        }

        private void SendCallback(IAsyncResult ar)
        {
            Socket handler = null;
            try
            {
                // Retrieve the socket from the state object.
                handler = (Socket)ar.AsyncState;
                #region msdn
                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
                //Console.WriteLine("Sent {0} bytes to client.", bytesSent);
                //Thread.Sleep(10000);
                //SocketInformLog("SendCallback;  bytesSent = " + bytesSent + Environment.NewLine);


                //handler.Shutdown(SocketShutdown.Both);
                //handler.Close();
                #endregion
                #region modify code
                // Complete sending the data to the remote device.
                //int bytesSent = handler.EndSend(ar);
                #endregion
            }
            catch (Exception e)
            {
                FireExceptionEvent(LEVSocketExceptionType.SendException, handler, "[Exception][SendCallback] Sends data asynchronously to a connected Socket Failure." + Environment.NewLine + e);
                //SocketInformLog("[Exception][SendCallback] Sends data asynchronously to a connected Socket Failure. ==> " + e + Environment.NewLine);
                //throw new Exception("[Exception] Sends data asynchronously to a connected Socket (SendCallback) Failure. ==> " + e);
                //log.Write("SendCallBack: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "SendCallBack: " + e.Message);
            }
        }

        // The method which delegate the Event, If anyone has to be notified
        public void SetSocketMsgEventHandler(SocketMsgEventHandler handler)
        {
            msgEventHandler = handler;
        }
        // The method which fires the Event, If anyone has subscribed, notify them
        private void FireSocketMsgEvent(LEVSocketEventType eventTyp, Socket client, string Msg)
        {
            if (msgEventHandler != null)
            {
                msgEventHandler(eventTyp, client, Msg);
            }
        }
        // The method which delegate the Event, If anyone has to be notified
        public void SetExceptionEventHandler(SocketExceptionEventHandler handler)
        {
            excptEventHandler = handler;
        }
        // The method which fires the Event, If anyone has subscribed, notify them
        private void FireExceptionEvent(LEVSocketExceptionType eventTyp, Socket client, string Msg)
        {
            if (excptEventHandler != null)
            {
                excptEventHandler(eventTyp, client, Msg);
            }
        }
        // The method which delegate the Event, If anyone has to be notified
        public void SetReceivingEventHandler(ReceivingEventHandler handler)
        {
            receiveEventHandler = handler;
        }
        // The method which fires the Event, If anyone has subscribed, notify them
        private void FireReceivingEvent(SocketReceivingTyp receiveType, Socket client, byte[] receivingData)
        {
            if (receiveEventHandler != null)
            {
                receiveEventHandler(receiveType, client, receivingData);
            }
        }
    }//public class LEVServerSocket
}//namespace LEVNet
